/*
 *Alarm.c
 *
 *  Created on: 19.09.2011
 *      Author: windows97
 */

#include <avr/io.h>
#include <stdlib.h>
#include <avr/eeprom.h>
#include "Globals.h"
#include "TimeDate.h"
#include "SoundControl.h"
#include "LightControl.h"
#include "ButtonControl.h"
#include "DisplayControl.h"
#include "ShiftRegControl.h"
#include "Alarm.h"


uint8_t NvmStorageAlarm_u8[64] EEMEM;

#define AL_DEBUG
#define ALARM_PRENOTIFICATION_TIME_S16  ((int16_t) (12/*hours*/ * TIME_MIN_MAX ))
#define ALARM_PENETRATION_TIME_INC_S16  ((int16_t) (2 /*minutes*/))
#define ALARM_PENETRATION_TIME_MAX_S16  ((int16_t) (-1.5/*hours*/ * TIME_MIN_MAX ))

typedef struct _T_AlarmNotification
{
  uint8_t AlarmChange_t   :1;
  uint8_t SecondChange_t  :1;
  uint8_t MinuteChange_t  :1;
  uint8_t ButtonEnter_t   :1;
  uint8_t ButtonSleep_t   :1;
}T_AlarmNotification;




static T_Alarm gAlarm_t[ALARM_CNT_U8];
static T_Alarm* gAlarmNext_pt = &gAlarm_t[0];

static T_AlarmNotification gAlarmIndication_t;

void AlaMinuteNotification( void );
void AlaButtonEnterNotification( T_ButtonReason iState_t );
void AlaButtonSleepNotification( T_ButtonReason  iState_t );
static uint8_t AlaConvertDay2AlaDay( uint8_t iDay_u8 );
static int16_t AlaCalcMinutes2Alarm( T_Alarm* iAlarm_t, T_TimeDate* iTimeDate_t );
static void AlaStateMachine( void );
static void AlaReadAlarms( void );
static void AlaStoreAlarms( void );
static void AlaDecTime2Al( void );


void AlaInit( void )
{
  gAlarmIndication_t.AlarmChange_t  = 0;
  gAlarmIndication_t.SecondChange_t = 0;
  gAlarmIndication_t.MinuteChange_t = 0;

  uint8_t lCnt_u8 = 0;
  /* initializing all alarms */
  for( lCnt_u8 = 0; lCnt_u8 < ALARM_CNT_U8; lCnt_u8++ )
  {
    gAlarm_t[lCnt_u8].State_t        = Alarm_Disabled;
    gAlarm_t[lCnt_u8].LightMode_t    = Alarm_LightOff;
    gAlarm_t[lCnt_u8].SoundMode_t    = Alarm_SoundOff;
    gAlarm_t[lCnt_u8].Days_t         = Alarm_NoDay;
    gAlarm_t[lCnt_u8].Time_t.Hour    = 0;
    gAlarm_t[lCnt_u8].Time_t.Minute  = 0;
    gAlarm_t[lCnt_u8].Time_t.Second  = 0;
    gAlarm_t[lCnt_u8].Sleep_t        = 0;
  }

  TidRegisterNotification( AlaMinuteNotification, MinuteChange );

#ifdef AL_DEBUG
  lCnt_u8 = 0;
  gAlarm_t[lCnt_u8].State_t         = Alarm_Enabled;
  gAlarm_t[lCnt_u8].LightMode_t     = Alarm_LightOff;
  gAlarm_t[lCnt_u8].SoundMode_t     = Alarm_Beep;
  gAlarm_t[lCnt_u8].Days_t          = Alarm_Mon | Alarm_Tue | Alarm_Wed | Alarm_Thu | Alarm_Fri;
  gAlarm_t[lCnt_u8].Time_t.Hour     = 5;
  gAlarm_t[lCnt_u8].Time_t.Minute   = 15;
  gAlarm_t[lCnt_u8].Time_t.Second   = 0;
  gAlarm_t[lCnt_u8].Sleep_t         = 5;
  lCnt_u8 = 1;
  gAlarm_t[lCnt_u8].State_t         = Alarm_Enabled;
  gAlarm_t[lCnt_u8].LightMode_t     = 45;
  gAlarm_t[lCnt_u8].SoundMode_t     = Alarm_SoundOff;
  gAlarm_t[lCnt_u8].Days_t          = Alarm_Sat | Alarm_Sun;
  gAlarm_t[lCnt_u8].Time_t.Hour     = 9;
  gAlarm_t[lCnt_u8].Time_t.Minute   = 00;
  gAlarm_t[lCnt_u8].Time_t.Second   = 0;
  gAlarm_t[lCnt_u8].Sleep_t         = 0;
#endif
  BcoRegisterNotification( AlaButtonEnterNotification, Button_Low2High, Button_Enter );
  BcoRegisterNotification( AlaButtonSleepNotification, Button_Low2High, Button_Cap );

  AlaReadAlarms();
  gAlarmIndication_t.AlarmChange_t = 1;
}

void AlaCyclic( void )
{
  if( ( 0 != gAlarmIndication_t.AlarmChange_t         ) &&
      ( Alarm_Active != gAlarmNext_pt->State_t) )
  {
    gAlarmIndication_t.AlarmChange_t = 0;
    T_TimeDate lTimeDate_t;
    TidGetTimeDate( &lTimeDate_t );
    uint8_t lNextAlarm_u8 = ALARM_NONE_U8;
    lNextAlarm_u8 = AlaGetNextAlarm( &lTimeDate_t );
    if( ALARM_NONE_U8 != lNextAlarm_u8 )
    {
      gAlarmNext_pt   = &gAlarm_t[lNextAlarm_u8];
      gAlarmNext_pt->Time2Al_s16 = AlaGetTime2NextAlarm( &lTimeDate_t );
    }
  }

  if( 0 != gAlarmIndication_t.MinuteChange_t )
  {
    gAlarmIndication_t.MinuteChange_t = 0;
    /* decrease time to next alarm */
    AlaDecTime2Al( );
    if( gAlarmNext_pt->State_t != Alarm_Enabled )
    {
      UART_PUT_VALUE("Time2Al: ", gAlarmNext_pt->Time2Al_s16);
    }
  }

  AlaStateMachine( );
}

uint8_t AlaGetNextAlarm( T_TimeDate* iTimeDate )
{
  uint16_t  lMinutes2Al = 0xFFFF;
  uint8_t   lAlarmSel = ALARM_NONE_U8;
  for( uint8_t lAlarmCnt = 0; lAlarmCnt < ALARM_CNT_U8; lAlarmCnt++)
  {
    /* check if alarm is generally enabled */
    if( Alarm_Enabled == (T_AlarmState)gAlarm_t[lAlarmCnt].State_t )
    {
      uint16_t lMinutes2AlCur_u16 = 0;
      /* Determine time to next alarm */
      lMinutes2AlCur_u16 = AlaCalcMinutes2Alarm( &gAlarm_t[lAlarmCnt], iTimeDate );
      /* check if this is the next alarm */
      if( lMinutes2AlCur_u16 < lMinutes2Al )
      {
        lMinutes2Al = lMinutes2AlCur_u16;
        lAlarmSel   = lAlarmCnt;
      }
    }
  }
  return (lAlarmSel);
}

void AlaMinuteNotification( void )
{
  gAlarmIndication_t.MinuteChange_t = 1;
}

uint8_t AlaIsAnyAlarmEnabled( void )
{
  uint8_t lCnt_u8     = 0;
  uint8_t lEnabled_u8 = 0;
  /* initializing all alarms */
  for( lCnt_u8 = 0; lCnt_u8 < ALARM_CNT_U8; lCnt_u8++ )
  {
    if( Alarm_Enabled == gAlarm_t[lCnt_u8].State_t )
    {
      lEnabled_u8 = 1;
      break;
    }
  }
  return (lEnabled_u8);
}

void AlaReadAlarms( void )
{
  //eeprom_read_block( &gAlarm_t, &NvmStorageAlarm_u8, sizeof(gAlarm_t));
  UART_PUT_STR( "Alarms read from EEPROM");
}

void AlaStoreAlarms( void )
{
  eeprom_write_block( &gAlarm_t, &NvmStorageAlarm_u8, sizeof(gAlarm_t));
  UART_PUT_STR( "Alarms written to EEPROM");

}

void AlaActivateAlarm(uint8_t iAlarmNo_u8 )
{
  /* check if input is valid */
  if( ALARM_CNT_U8 > iAlarmNo_u8 )
  {
    if( gAlarm_t[iAlarmNo_u8].LightMode_t != Alarm_LightOff )
    {

    }
  }

}

static uint8_t AlaConvertDay2AlaDay( uint8_t iDay_u8 )
{
  uint8_t lRetDay_u8 = 0;
  switch( (T_WeekDay) iDay_u8 )
  {
    /* determine week day */
    /*
     * the realisation with shift operation
     * lRetday_u8 = 1 << iDay_u8-1;
     * would be also possible (but might be less robust)
     */
    case Monday:
    {
      lRetDay_u8 = Alarm_Mon;
      break;
    }
    case Tuesday:
    {
      lRetDay_u8 = Alarm_Tue;
      break;
    }
    case Wednesday:
    {
      lRetDay_u8 = Alarm_Wed;
      break;
    }
    case Thursday:
    {
      lRetDay_u8 = Alarm_Thu;
      break;
    }
    case Friday:
    {
      lRetDay_u8 = Alarm_Fri;
      break;
    }
    case Saturday:
    {
      lRetDay_u8 = Alarm_Sat;
      break;
    }
    case Sunday:
    {
      lRetDay_u8 = Alarm_Sun;
      break;
    }
    default:
    {
      break;
    }
  }
  return (lRetDay_u8);
}


static int16_t AlaCalcMinutes2Alarm( T_Alarm* iAlarm_t, T_TimeDate* iTimeDate_t )
{
  int16_t  lMin2Alarm_u16     = 32767;
  uint16_t lMinutes2AlCur_u16 = 0;
  uint16_t lMinutesOfDay_u16  = 0;
  uint16_t lMinutesOfDayIncrement_u16 = 0;
  /* Determine time to next alarm */
  lMinutes2AlCur_u16  = (( iAlarm_t->Time_t.Hour * TIME_MIN_MAX ) + iAlarm_t->Time_t.Minute );
  lMinutesOfDay_u16   = (( iTimeDate_t->Time.Hour * TIME_MIN_MAX ) + iTimeDate_t->Time.Minute );

  for( uint8_t lDayCount_u8 = iTimeDate_t->Date.WeekDay; lDayCount_u8 < iTimeDate_t->Date.WeekDay+Sunday; lDayCount_u8++ )
  {
    uint8_t lTempDay_u8;
    if( lDayCount_u8 <= Sunday)
    {
      lTempDay_u8 = AlaConvertDay2AlaDay( lDayCount_u8 );
    }
    else
    {
      lTempDay_u8 = AlaConvertDay2AlaDay( lDayCount_u8-Sunday );
    }
    /* check if the alarm is enabled on this weekday */
    if( 0 != (lTempDay_u8 & iAlarm_t->Days_t) )
    {
      /* increment for the next day */
      lMinutesOfDayIncrement_u16 = ( TIME_HOUR_MAX * TIME_MIN_MAX ) * (lDayCount_u8-iTimeDate_t->Date.WeekDay) ;
      /* the alarm is enabled on this weekday */
      if( (lMinutesOfDayIncrement_u16 + lMinutes2AlCur_u16) >=  lMinutesOfDay_u16 )
      {
        if( 0x7FFF >= lMinutesOfDayIncrement_u16 + lMinutes2AlCur_u16 - lMinutesOfDay_u16 )
        {
          lMin2Alarm_u16 = (int16_t)lMinutesOfDayIncrement_u16 + lMinutes2AlCur_u16 - lMinutesOfDay_u16;
        }
        /* work finished for this alarm
         * let's leave the loop for this alarm
         */
        break;
      }
    }
  }
  return ( lMin2Alarm_u16 );
}

static void AlaStateMachine( void )
{
  switch( gAlarmNext_pt->State_t )
  {
    case Alarm_Enabled:
    {
      if( (gAlarmNext_pt->LightMode_t) >= gAlarmNext_pt->Time2Al_s16 )
      {
        gAlarmNext_pt->State_t = Alarm_PreActive;
      }
      break;
    }
    case Alarm_PreActive:
    {
      /* check for light controlled alarm */
      if( Alarm_LightOff != gAlarmNext_pt->LightMode_t )
      {
        uint8_t lLightPercent_u8;
        lLightPercent_u8 = ((uint8_t)(100 - ((gAlarmNext_pt->Time2Al_s16*100)/gAlarmNext_pt->LightMode_t)));
        LcoSetBrightness( lLightPercent_u8 );
        LcoSetLight( LightOn );
      }
      /* state transition checking */
      if ( 0 == gAlarmNext_pt->Time2Al_s16 )
      {
        gAlarmNext_pt->State_t = Alarm_Active;
      }
      else if( 0 != gAlarmIndication_t.ButtonEnter_t )
      {
        LcoSetLight( LightOff );
        gAlarmNext_pt->State_t = Alarm_Deactive;
      }
      break;
    }
    case Alarm_Active:
    {
      /* sound handling */
      if( Alarm_SoundOff != gAlarmNext_pt->SoundMode_t )
      {
        T_ShiftValues lVal_t = { 0x0F };
        ScoSetSound( SoundOn );
        SrcSetValues( &lVal_t );
      }
      /* light handling */
      if( Alarm_LightOff != gAlarmNext_pt->LightMode_t )
      {
        /* full light */
        LcoSetBrightness( 100 );
      }
      DispToggleState( DispFast );
      /* state transition checking */
      if( ALARM_PENETRATION_TIME_INC_S16-1 == (abs(gAlarmNext_pt->Time2Al_s16)%ALARM_PENETRATION_TIME_INC_S16) )
      {
        ScoDecBeepMode( );
      }
      if( ALARM_PENETRATION_TIME_MAX_S16 >= gAlarmNext_pt->Time2Al_s16 )
      {
        LcoSetLight( LightOff );
        ScoSetSound( SoundOff );
        DispSetState( DispOff, DispSmooth );
        gAlarmNext_pt->State_t = Alarm_Deactive;
      }
      if( 0 != gAlarmIndication_t.ButtonEnter_t )
      {
        T_ShiftValues lVal_t = { 0x00 };
        LcoSetLight( LightOff );
        ScoSetSound( SoundOff );
        DispSetState( DispOff, DispSmooth );
        SrcSetValues( &lVal_t );
        gAlarmNext_pt->State_t = Alarm_Deactive;
      }
      if( 0 != gAlarmIndication_t.ButtonSleep_t )
      {
        gAlarmNext_pt->Time2Al_s16 = 0;
        gAlarmIndication_t.ButtonSleep_t = 0;
        ScoSetSound( SoundOff );
        DispSetState( DispOff, DispSmooth );
        if( ALARM_SLEEP_OFF_U8 == gAlarmNext_pt->Sleep_t )
        {
          gAlarmNext_pt->State_t = Alarm_Deactive;
        }
        else
        {
          gAlarmNext_pt->State_t = Alarm_Sleep;
        }
      }
      break;
    }
    case Alarm_Sleep:
    {
      /* state transition checking */
      if( 0 != gAlarmIndication_t.ButtonEnter_t )
      {
        ScoSetSound( SoundOff );
        LcoSetLight( LightOff );
        gAlarmNext_pt->State_t = Alarm_Deactive;
      }
      else if( 0 != gAlarmNext_pt->Time2Al_s16 )
      {
        if( 0 == ( gAlarmNext_pt->Time2Al_s16 % gAlarmNext_pt->Sleep_t) )
        {
          gAlarmNext_pt->State_t = Alarm_Active;
        }
      }
      break;
    }
    case Alarm_Deactive:
    {
      if( 0 > gAlarmNext_pt->Time2Al_s16 )
      {
        if( Alarm_Once == gAlarmNext_pt->Days_t )
        {
          gAlarmNext_pt->State_t = Alarm_Disabled;
        }
        else
        {
          gAlarmNext_pt->State_t = Alarm_Enabled;
        }
        gAlarmIndication_t.AlarmChange_t = 1;
      }
      break;
    }
    case Alarm_Disabled:
    default:
    {
      /* nothing to do */
      break;
    }
  }
  /*
   * reset all button indications , because if they have not been handled within this state run
   * they are obsolete and though should be reset
   * This should not lead to any rising conditions because the button handling will not call the notification during the Cyclic Operation of the alarm handling
   */
  gAlarmIndication_t.ButtonEnter_t = 0;
  gAlarmIndication_t.ButtonSleep_t = 0;
}

int16_t AlaGetTime2NextAlarm( T_TimeDate* lTimeDate_t )
{
  return (AlaCalcMinutes2Alarm( &gAlarm_t[AlaGetNextAlarm( lTimeDate_t )], lTimeDate_t ));
}

uint8_t AlaGetTimeOfNextAlarm( T_Time* lAlarmTime_t )
{
  uint8_t lRetVal_t = ALARM_NONE_U8;
  if( ALARM_PRENOTIFICATION_TIME_S16 >= gAlarmNext_pt->Time2Al_s16 )
  {
    lRetVal_t = ALARM_CNT_U8;
    *lAlarmTime_t = gAlarmNext_pt->Time_t;
  }
  return (lRetVal_t);
}

void AlaDeactivateAlarm( void )
{
  if( Alarm_Active == gAlarmNext_pt->State_t )
  {
    gAlarmNext_pt->State_t = Alarm_Enabled;
    ScoSetSound( SoundOff );
    LcoSetLight( LightOff );
  }
  gAlarmIndication_t.AlarmChange_t = 1;
}

void AlaButtonEnterNotification( T_ButtonReason  iState_t )
{
  gAlarmIndication_t.ButtonEnter_t = 1;
}

void AlaButtonSleepNotification( T_ButtonReason  iState_t )
{
  gAlarmIndication_t.ButtonSleep_t = 1;
}

uint8_t AlaSetAlarm( T_Alarm* iAlarmData_t, uint8_t iAlarmIndex_u8 )
{
  uint8_t lRetVal_u8 = E_WRONG_VALUE;

  if( iAlarmIndex_u8 <= ALARM_CNT_U8 )
  {
    gAlarm_t[iAlarmIndex_u8] = *iAlarmData_t;
    gAlarmIndication_t.AlarmChange_t = 1;
    AlaStoreAlarms( );
    lRetVal_u8 = E_OK;
  }
  return (lRetVal_u8);
}

static void AlaDecTime2Al( void )
{
  /* decrease all alarms */
  for( uint8_t lCnt_u8 = 0; lCnt_u8 < ALARM_CNT_U8; lCnt_u8++ )
  {
    --gAlarm_t[lCnt_u8].Time2Al_s16;
  }
}

uint8_t AlaGetState( void )
{
  return( (uint8_t) gAlarmNext_pt->State_t );
}
